package com.ormbench.base.model;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.lang.time.StopWatch;
import org.h2.tools.RunScript;

import com.ormbench.base.dao.DAOFactory;
import com.ormbench.ebean.EbeanDAOFactory;
import com.ormbench.hibernate.HibernateDAOFactory;

public class OrmBench {
	
	// Supported ORMs
	public static final String ORM_EBEAN = "Ebean";
	public static final String ORM_HIBERNATE = "Hibernate";
	
	// Default Settings
	private static final int DEFAULT_SCALE = 1;
	private static final String DEFAULT_JDBC_DRIVER = "org.h2.Driver";
	private static final String DEFAULT_JDBC_URL = "jdbc:h2:~/ormbench";
	private static final String DEFAULT_JDBC_USERNAME = "sa";
	private static final String DEFAULT_JDBC_PASSWORD = "";
	private static final String SQL_CREATE_SCRIPT = "create.sql";
	
	private String orm;
	private DAOFactory daoFactory;
	private int branches = 1;
	private int tellers = 10;
	private int accounts = 100000;

	private Integer scale = DEFAULT_SCALE;
	public Integer getScale() { return scale; }
	public void setScale(Integer scale) { this.scale = scale; }
	
	private String jdbcDriver = DEFAULT_JDBC_DRIVER;
	public String getJdbcDriver() { return jdbcDriver; }
	public void setJdbcDriver(String jdbcDriver) { this.jdbcDriver = jdbcDriver; }
	
	private String jdbcUrl = DEFAULT_JDBC_URL;
	public String getJdbcUrl() { return jdbcUrl; }
	public void setJdbcUrl(String jdbcUrl) { this.jdbcUrl = jdbcUrl; }
	
	private String jdbcUsername = DEFAULT_JDBC_USERNAME;
	public String getJdbcUsername() { return jdbcUsername; }
	public void setJdbcUsername(String jdbcUsername) { this.jdbcUsername = jdbcUsername; }

	private String jdbcPassword = DEFAULT_JDBC_PASSWORD;
	public String getJdbcPassword() { return jdbcPassword; }
	public void setJdbcPassword(String jdbcPassword) { this.jdbcPassword = jdbcPassword; }

	public Map<String, Long> run(String orm) throws Exception {
		this.orm = orm;
		branches *= scale;
		tellers *= scale;
		accounts *= scale;
		
		// Maybe Passwordless
		if (jdbcPassword == null) {
			jdbcPassword = DEFAULT_JDBC_PASSWORD;
		}
		
		Map<String, Long> results = new LinkedHashMap<String, Long>();
		
		initDB();
		initORM();
		
		results.put("insert", insertTest());
		results.put("select", selectTest());
		results.put("update", updateTest());
		results.put("delete", deleteTest());
		
		return results;
	}
	
	
	/**
	 * Init DB Schema
	 */
	private void initDB() throws Exception {
		ClassLoader cl = this.getClass().getClassLoader();
		Reader reader = new BufferedReader(new InputStreamReader(cl.getResourceAsStream(SQL_CREATE_SCRIPT)));
		Class.forName(jdbcDriver);
		Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUsername, jdbcPassword);
		RunScript.execute(conn, reader);
		conn.close();
	}
	
	/**
	 * Init ORM
	 */
	public void initORM() throws Exception {
		// Get DAO Factory
		if (ORM_EBEAN.equalsIgnoreCase(this.orm)) {
			daoFactory = new EbeanDAOFactory();
		} else if (ORM_HIBERNATE.equalsIgnoreCase(this.orm)) {
			daoFactory = new HibernateDAOFactory();
		} else {
			throw new Exception("Unsupported ORM: " + this.orm);
		}
		
		// Configure
		daoFactory.configure(jdbcDriver, jdbcUrl, jdbcUsername, jdbcPassword);
	}
	
	public Long insertTest() {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		String admin = "ormbench";
		Date now = new Date();
		
		// Start Transaction
		daoFactory.beginTransaction();
		
		// Branches
		for (int b = 0; b < branches; b++) {
			Branch branch = new Branch();
			branch.setBalance(0);
			branch.setCreatedBy(admin);
			branch.setCreatedDate(now);
			branch.setUpdatedBy(admin);
			branch.setUpdatedDate(now);
			daoFactory.getBranchDAO().create(branch);
			
			// Tellers
			for (int t = 0; t < tellers; t++) {
				Teller teller = new Teller();
				teller.setBranchId(branch.getId());
				teller.setBalance(0);
				teller.setCreatedBy(admin);
				teller.setCreatedDate(now);
				teller.setUpdatedBy(admin);
				teller.setUpdatedDate(now);
				daoFactory.getTellerDAO().create(teller);
			}
			
			// Accounts
			for (int a = 0; a < accounts; a++) {
				Account account = new Account();
				account.setBranchId(branch.getId());
				account.setBalance(0);
				account.setCreatedBy(admin);
				account.setCreatedDate(now);
				account.setUpdatedBy(admin);
				account.setUpdatedDate(now);
				daoFactory.getAccountDAO().create(account);
			}
		}
		
		// Commit Transaction
		daoFactory.commitTransaction();
		
		stopWatch.stop();
		return stopWatch.getTime();
	}
	
	public Long selectTest() {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		for (int a = 0; a < accounts; a++) {
			int accountId = RandomUtils.nextInt(accounts) + 1;
			
			daoFactory.beginTransaction();
			Account account = daoFactory.getAccountDAO().read(Long.valueOf(accountId));
			account.getBalance();
			Branch branch = account.getBranch();
			List<Teller> tellers = branch.getTellers();
			tellers.size();
			List<History> histories = branch.getHistories();
			histories.size();
			daoFactory.commitTransaction();
		}
		
		stopWatch.stop();
		return stopWatch.getTime();
	}
	
	public Long updateTest() {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		for (int a = 0; a < accounts; a++) {
			String admin = "ormbench";
			Date now = new Date();
			int branchId = RandomUtils.nextInt(branches) + 1;
			int tellerId  = RandomUtils.nextInt(tellers) + 1;
			int accountId = RandomUtils.nextInt(accounts) + 1;
			int delta  = RandomUtils.nextInt(10000) + 1;
			
			daoFactory.beginTransaction();
			
			Branch branch = daoFactory.getBranchDAO().read(Long.valueOf(branchId));
			branch.setBalance(branch.getBalance() + delta);
			branch.setUpdatedBy(admin);
			branch.setUpdatedDate(now);
			daoFactory.getBranchDAO().update(branch);
			
			Teller teller = daoFactory.getTellerDAO().read(Long.valueOf(tellerId));
			teller.setBalance(branch.getBalance() + delta);
			teller.setUpdatedBy(admin);
			teller.setUpdatedDate(now);
			daoFactory.getTellerDAO().update(teller);
			
			Account account = daoFactory.getAccountDAO().read(Long.valueOf(accountId));
			account.setBalance(account.getBalance() + delta);
			account.setUpdatedBy(admin);
			account.setUpdatedDate(now);
			daoFactory.getAccountDAO().update(account);
			
			History history = new History();
			history.setBranchId(branch.getId());
			history.setTellerId(teller.getId());
			history.setAccountId(account.getId());
			history.setDelta(delta);
			history.setCreatedBy(admin);
			history.setCreatedDate(now);
			history.setUpdatedBy(admin);
			history.setUpdatedDate(now);
			daoFactory.getHistoryDAO().create(history);
			
			daoFactory.commitTransaction();
		}
		
		stopWatch.stop();
		return stopWatch.getTime();
	}
	
	public Long deleteTest() {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		// Histories
		daoFactory.beginTransaction();
		daoFactory.getHistoryDAO().deleteAll();
		daoFactory.commitTransaction();
			
		// Accounts
		daoFactory.beginTransaction();
		daoFactory.getAccountDAO().deleteAll();
		daoFactory.commitTransaction();
		
		// Tellers
		daoFactory.beginTransaction();
		daoFactory.getTellerDAO().deleteAll();
		daoFactory.commitTransaction();
		
		// Branchs
		daoFactory.beginTransaction();
		daoFactory.getBranchDAO().deleteAll();
		daoFactory.commitTransaction();
		
		stopWatch.stop();
		return stopWatch.getTime();
	}
}
